#include <core/vmspace.h>
#include <core/vmo.h>
#include <task.h>
#include <radix.h>
#include <malloc.h>
#include <uaccess.h>
#include <log.h>

class_impl(vmobject_t, kobject_t){};

void vmo_init(vmobject_t *vmo, vmo_type_t type, size_t len, paddr_t paddr)
{
    size_t page_number = PGROUNDUP(len) >> PAGE_SHIFT;

    vmo->size = page_number * PAGE_SIZE;
    vmo->type = type;

    /* for a VMO_DATA, the user will use it soon (we expect) */
    if (type == VMO_DATA)
    {
        /* kmalloc(>2048) returns continous physical pages */
        vmo->start = (paddr_t)alloc_page(page_number);
    }
    else if (type == VMO_DEVICE)
    {
        vmo->start = paddr;
    }
    else
    {
        /*
         * for stack, heap, we do not allocate the physical memory at
         * once
         */
        vmo->radix = new_radix();
        init_radix(vmo->radix);
    }
}

vmobject_t *vmo_create(u64_t size, u64_t type)
{
    vmobject_t *vmo = new (vmobject_t);

    if (vmo == NULL)
        return NULL;
    vmo_init(vmo, type, size, 0);

    return vmo;
}

slot_t sys_vmo_create(u64_t size, u64_t type)
{
    vmobject_t *vmo = new (vmobject_t);

    if (vmo == NULL)
        goto out_fail;
    vmo_init(vmo, type, size, 0);

    slot_t slot = slot_alloc_install(process_self(), vmo);
    if (slot == -1)
        goto out_free_obj;
    return slot;
out_free_obj:
    delete (vmo);
out_fail:
    return -1;
}

#define WRITE_VMO 0
#define READ_VMO 1

static int read_write_vmo(slot_t slot, u64_t offset, u64_t user_buf,
                          u64_t size, u64_t type)
{
    vmobject_t *vmo;
    int r = 0;

    /* caller should have the slot */
    vmo = dynamic_cast(vmobject_t)(slot_get(process_self(), slot));
    if (!vmo)
    {
        r = -1;
        goto out_fail;
    }

    /* we only allow writing VMO_DATA now. */
    if (vmo->type != VMO_DATA)
    {
        r = -1;
        goto out_fail;
    }

    if (offset + size < offset || offset + size > vmo->size)
    {
        r = -1;
        goto out_fail;
    }

    if (type == WRITE_VMO)
        r = copy_from_user((char *)phys_to_virt(vmo->start) + offset,
                           (char *)user_buf, size);
    else if (type == READ_VMO)
        r = copy_to_user((char *)user_buf,
                         (char *)phys_to_virt(vmo->start) + offset,
                         size);
    else
        PANIC("read write vmo invalid type\n");

out_fail:
    return r;
}

int sys_vmo_write(slot_t slot, u64_t offset, u64_t user_ptr, u64_t len)
{
    return read_write_vmo(slot, offset, user_ptr, len, WRITE_VMO);
}

int sys_vmo_read(slot_t slot, u64_t offset, u64_t user_ptr, u64_t len)
{
    return read_write_vmo(slot, offset, user_ptr, len, READ_VMO);
}

void *vmo_map_on_vmspace(vmobject_t *vmo, vmspace_t *vmspace, u64_t addr, u64_t prot, u64_t flags)
{
    int r;

    if (addr == NULL)
    {
        addr = vmspace_find_unmaped(vmspace, vmo->size);

        if (addr == NULL)
        {
            LOGE("Unable to find free address space");
            r = -1;
            goto out_fail;
        }
    }

    bool_t ret = vmspace_map_range_user(vmspace, addr, vmo->size, prot, flags, vmo);

    if (!ret)
    {
        r = -1;
        goto out_fail;
    }

    return addr;
out_fail:
    LOGE("Failed to exec vmo_map");
    return r;
}

void *vmo_map(vmobject_t *vmo, u64_t addr, u64_t prot, u64_t flags)
{
    return vmo_map_on_vmspace(vmo, thread_self()->vmspace, addr, prot, flags);
}

void *sys_vmo_map(slot_t slot, u64_t addr, u64_t prot, u64_t flags)
{
    vmobject_t *vmo = dynamic_cast(vmobject_t)(slot_get(process_self(), slot));

    if (!vmo)
        return -1;

    return vmo_map(vmo, addr, prot, flags);
}

void *sys_vmo_map_to_process(slot_t vmo_slot, slot_t process_slot, u64_t addr, u64_t prot, u64_t flags)
{
    vmobject_t *vmo = dynamic_cast(vmobject_t)(slot_get(process_self(), vmo_slot));
    process_t *process = dynamic_cast(process_t)(slot_get(process_self(), process_slot));
    LOGI($(vmo) " "$(process) " " $(process_slot));
    if (!vmo)
        return -1;

    return vmo_map_on_vmspace(vmo, process->vmspace, addr, prot, flags);
}